package study.domain.blog;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.Lob;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;

import lombok.AccessLevel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;

import org.apache.commons.io.IOUtils;

import study.domain.user.User;

import com.barney.support.domain.HasCreatedAndUpdatedDate;
import com.barney.support.domain.HasPostLoad;
import com.barney.support.domain.HasPrePersistAndPreUpdate;
import com.barney.support.jpa.CreatedAndUpdatedDateListener;
import com.barney.support.jpa.PostLoadListener;
import com.barney.support.jpa.PrePersistAndPreUpdateListener;
import com.google.common.collect.Lists;

/**
 * timeline 용 수신함
 *
 * @author Barney Kim
 */
@Entity(name = "inboxes")
@Data
@EqualsAndHashCode(of = { "id" })
@EntityListeners({ CreatedAndUpdatedDateListener.class, PostLoadListener.class,
		PrePersistAndPreUpdateListener.class })
public class Inbox implements Serializable, HasCreatedAndUpdatedDate,
		HasPostLoad, HasPrePersistAndPreUpdate {

	private static final long serialVersionUID = 5212592192184752108L;

	public static int MAX_DATA_SIZE = 200;

	@EmbeddedId
	private InboxId id;

	@Lob
	@Column(nullable = false)
	private byte[] data;

	@Temporal(TemporalType.TIMESTAMP)
	@Column(nullable = false, updatable = false)
	private Date createdDate;

	@Temporal(TemporalType.TIMESTAMP)
	@Column(nullable = false, updatable = false)
	private Date updatedDate;

	@Transient
	@Getter(value = AccessLevel.NONE)
	@Setter(value = AccessLevel.NONE)
	private List<InboxData> dataList = Lists.newArrayList();

	public Inbox() {
		//
	}

	public Inbox(Long userId) {
		this(new User(userId));
	}

	public Inbox(User user) {
		this(new InboxId(user));
	}

	public Inbox(InboxId id) {
		this.id = id;
	}

	/**
	 * 수신함에 포스트를 추가한다.
	 */
	public void addPost(Post post) {
		if (dataList == null) {
			dataList = Lists.newArrayList();
		}
		if (dataList.add(new InboxData(post))) {
			saveInboxData();
		}
	}

	/**
	 * 수신함에서 포스트를 삭제한다.
	 */
	public boolean deletePost(Post post) {
		if (dataList == null || dataList.size() == 0) {
			return false;
		}
		InboxData key = new InboxData(post);
		if (!dataList.contains(key)) {
			return false;
		}
		if (dataList.remove(key)) {
			saveInboxData();
			return true;
		}
		return false;
	}

	/**
	 * 수신함에서 특정 사용자의 글을 모두 삭제한다.
	 */
	public int deleteAllByUserId(Long userId) {
		if (dataList == null || dataList.size() == 0) {
			return 0;
		}
		int count = 0;
		InboxData key = new InboxData(userId);
		while (dataList.contains(key)) {
			if (dataList.remove(key)) {
				count++;
			}
		}
		if (count > 0) {
			saveInboxData();
		}
		return count;
	}

	/**
	 * 수신함에 들어있는 포스트 ID 배열을 반환한다.
	 */
	public List<Long> getPostIds() {
		List<Long> ids = Lists.newArrayList();
		if (dataList != null && dataList.size() > 0) {
			for (InboxData d : dataList) {
				ids.add(d.getId());
			}
		}
		return ids;
	}

	/**
	 * 데이터 목록 크기를 반환한다.
	 */
	public int getDataListSize() {
		return (dataList != null) ? dataList.size() : 0;
	}

	/**
	 * 엔티티가 로드되면 바이트 데이터를 로드하여 데이터 목록을 만든다.
	 */
	@Override
	public void postLoad() {
		loadInboxData();
	}

	/**
	 * 저장하거나 수정하기 전에 데이터 목록을 데이터 최대 크기 만큼 조정한다.
	 * @see #MAX_DATA_SIZE
	 */
	@Override
	public void prePersistAndPreUpdate() {
		if (data != null && (dataList == null || dataList.size() == 0)) {
			loadInboxData();
		}
		if (data != null) {
			Collections.sort(dataList);
			Collections.reverse(dataList);
			int total = dataList.size();
			if (total > MAX_DATA_SIZE) {
				List<InboxData> tmpList = Lists.newArrayList();
				dataList = dataList.subList(0, MAX_DATA_SIZE);
				tmpList.addAll(dataList);
				dataList = tmpList;
			}
		}
		saveInboxData();
	}

	/**
	 * 바이트 데이터를 읽어서 데이터 목록을 반환한다.
	 */
	@SuppressWarnings("unchecked")
	private void loadInboxData() {
		if (data == null) {
			return;
		}
		ObjectInputStream istream = null;
		try {
			istream = new ObjectInputStream(new ByteArrayInputStream(data));
			dataList = (List<InboxData>) istream.readObject();
		} catch (Exception ex) {
			ex.printStackTrace();
		} finally {
			IOUtils.closeQuietly(istream);
		}
	}

	/**
	 * 수신함 데이터를 역순으로 정렬하여 바이트로 쓰기
	 */
	private void saveInboxData() {
		if (dataList == null) {
			dataList = Lists.newArrayList();
		}
		Collections.sort(dataList);
		Collections.reverse(dataList);

		ByteArrayOutputStream baos = null;
		ObjectOutputStream ostream = null;
		try {
			baos = new ByteArrayOutputStream();
			ostream = new ObjectOutputStream(baos);
			ostream.writeObject(dataList);
			ostream.flush();
			data = baos.toByteArray();
		} catch (Exception ex) {
			ex.printStackTrace();
		} finally {
			IOUtils.closeQuietly(ostream);
			IOUtils.closeQuietly(baos);
		}
	}
}
